package parsers;

import com.google.gdata.util.ServiceException;
import gui.MainFrame;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.LinkedList;
import java.util.List;
import javax.mail.MessagingException;
import notifiers.MailBuilder;
import notifiers.MailSender;
import notifiers.SmsSender;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import rsshunter.RSSBase;
import rsshunter.RSSChannel;
import rsshunter.RSSKeyword;

/**
 * HtmlParser is checking for new html content in html-type RSSBases.
 */
public class HtmlParser extends AbstractParser {

    List<RSSChannel> channels;
    List<RSSKeyword> tags;
    RSSBase actualBase;
    List<String> newContentsList;

    public HtmlParser(MainFrame parent, MailSender mailSender,
            SmsSender smsSender) {
        super(parent, mailSender, smsSender);
        newContentsList = new LinkedList<String>();
    }

    /**
     * Saves content for newly created channel.
     *
     * @param channelsList List of channels to init.
     * @param tagsList List of tags in initting RSSBase.
     */
    public static void initChannels(List<RSSChannel> channelsList,
            List<RSSKeyword> tagsList) throws MalformedURLException {
        for (RSSChannel channel : channelsList) {
            String path = getChannelPath(channel);
            String content;
            if (tagsList.isEmpty()) {
                content = getHtmlContent(channel.getURI());
                if (content.isEmpty()) {
                    throw new MalformedURLException(channel.getURI()); //URL is checked only once when channel is inited
                }
            } else {
                content = getSpecificHtmlContent(channel.getURI(), tagsList);
            }
            saveContent(content, path);
        }
    }

    /**
     * @return Path to file with content of given channel.
     */
    public static String getChannelPath(RSSChannel channel) {
        return "HtmlContents/" + channel.getId() + ".html";
    }

    /**
     * Delete file with page content for given RSSChannel.
     */
    public static void deletePageContentFile(RSSChannel channel) {
        String path = getChannelPath(channel);
        File file = new File(path);
        if (file.exists()) {
            file.delete();
        }
    }

    /**
     * Get content from given file path.
     *
     * @param path
     * @return String with content.
     */
    public static String getFileContent(String path) {
        try {
            File file = new File(path);
            String content = getContent(new FileInputStream(file));
            return content;
        } catch (IOException e) {
            System.err.println(
                    "Error getting content from file " + path + ": " + e);
            return "";
        }
    }

    /**
     * Get html content from given uri.
     *
     * @param uri
     * @return String with content.
     */
    public static String getHtmlContent(String uri) {
        try {
            URL pageUrl = new URL(uri);
            URLConnection uc = pageUrl.openConnection();
            String content = getContent(uc.getInputStream());
            return content;
        } catch (IOException e) {
            System.err.println(
                    "Error getting html content from uri " + uri + ": " + e);
            return "";
        }
    }

    /**
     * Get only given html tags from uri.
     *
     * @param uri Uri to parse.
     * @param tagsList List of tags.
     * @return String with tag specific content.
     */
    public static String getSpecificHtmlContent(String uri, List<RSSKeyword> tagsList) {
        String entireContent = getHtmlContent(uri);
        Document doc = Jsoup.parse(entireContent);
        Element el = doc.body();
        StringBuilder content = new StringBuilder();
        for (RSSKeyword tag : tagsList) {
            Elements elements = el.getElementsByTag(tag.getValue());
            content.append(elements.toString());
        }
        return content.toString();
    }

    /**
     * Get content from given input.
     *
     * @param input InputStream from url or file.
     * @return String with content.
     * @throws IOException
     */
    private static String getContent(InputStream input) throws IOException {
        final char[] buffer = new char[0x10000];
        StringBuilder out = new StringBuilder();
        Reader in = new InputStreamReader(input, "UTF-8");
        int read;
        do {
            read = in.read(buffer, 0, buffer.length);
            if (read > 0) {
                out.append(buffer, 0, read);
            }
        } while (read >= 0);

        return out.toString();
    }

    /**
     * Saves given content into file.
     *
     * @param content String with content.
     * @param path Path to file where content should be saved.
     */
    public static void saveContent(String content, String path) {
        File file = new File(path);
        if (!file.exists()) {
            try {
                file.createNewFile();
            } catch (IOException ex) {
                System.err.println("Couldn't create new file for content "
                        + path + " : " + ex);
                return;
            }
        }

        try {
            PrintWriter pw = new PrintWriter(file);
            pw.print(content);
            pw.flush();
        } catch (FileNotFoundException ex) {
            System.err.println("File " + path + "not found:" + ex);
        }
    }

    @Override
    protected void parse(RSSBase base) {
        newContentsList.clear();
        channels = getChannels(base);
        tags = getKeywords(base);
        actualBase = base;
        int steps = 2 + (channels.size() * 2);
        progressStep = (int) Math.floor(100 / steps);
        incrementProgress();

        if (tags.isEmpty()) {
            parseEntireContent();
        } else {
            parseSpecificTags();
        }
        notifyUser();
        incrementProgress();
        if (newContentsList.size() == 1) {
            parentFrame.displayTrayMessage("RSSHunter - page content changed",
                    "Content at page " + newContentsList.get(0)
                    + " has changed.");
        } else if (!newContentsList.isEmpty()) {
            parentFrame.displayTrayMessage("RSSHunter - page content changed",
                    "Page content at " + newContentsList.size()
                    + " pages in base " + actualBase.getName() + " has changed");
        }

    }

    /**
     * Parse entire content from all channels in base.
     */
    private void parseEntireContent() {
        String path;
        String uri;
        for (RSSChannel channel : channels) {
            path = getChannelPath(channel);
            uri = channel.getURI();
            String oldContent = getFileContent(path);
            String newContent = getHtmlContent(uri);
            incrementProgress();
            if (!oldContent.equals(newContent)) {
                newContentsList.add(channel.getURI());
                saveContent(newContent, path);
            }
            incrementProgress();
        }
    }

    /**
     * Parse content with only specific tags.
     */
    private void parseSpecificTags() {
        String path;
        String uri;
        for (RSSChannel channel : channels) {
            path = getChannelPath(channel);
            uri = channel.getURI();
            String oldContent = getFileContent(path);
            String newContent = getSpecificHtmlContent(uri, tags);
            incrementProgress();
            if (!oldContent.equals(newContent)) {
                newContentsList.add(channel.getURI());
                saveContent(newContent, path);
            }
            incrementProgress();
        }
    }

    /**
     * Notify user about changed html content.
     */
    private void notifyUser() {
        if (newContentsList.isEmpty()) {
            return; // Nothing to notify
        }
        if (actualBase.getNotify().equals("Sms")) {
            String message = smsSender.composeHtmlNotification(
                    newContentsList, actualBase.getName());
            try {
                smsSender.sendSMS(message);
            } catch (ServiceException ex) {
                displayAuthenticationError(
                        "Couldn't send SMS notification for base "
                        + actualBase.getName() + " : Bad login or password.",
                        "Bad gmail login or password");
            }

        } else if (actualBase.getNotify().equals("E-mail")) {
            String message = MailBuilder.composeHtmlNotification(
                    newContentsList, actualBase.getName());
            try {
                mailSender.sendMail(
                        "RSSHunter - new content in base " + actualBase.getName(),
                        message);
            } catch (MessagingException ex) {
                displayAuthenticationError(
                        "Couldn't send e-mail notification for base "
                        + actualBase.getName() + " : Bad login or password.",
                        "Bad gmail login or password");
            }
        }
    }
}
